use std::path::Path;

use bc_base::{BlockHeight, BlockId, TimeStamp, block::Block};

use crate::BlockDataStorageError;
use crate::inner_storage::misc_item::MiscItemDbKey;
use crate::inner_storage::misc_item::MiscItemStorageBase;

#[derive(Debug)]
pub struct MiscItemStorage {
    storage_base: MiscItemStorageBase,
}

impl MiscItemStorage {
    pub fn new(db_root_dir: &Path) -> Result<Self, BlockDataStorageError> {
        let path = crate::get_misc_item_db_path(db_root_dir);
        tracing::info!(
            ">>>> The path of MiscItemStorage DB: {}",
            path.to_str().unwrap()
        );
        Ok(Self {
            storage_base: MiscItemStorageBase::new(&path)?,
        })
    }
    pub async fn flush_database(&self) -> Result<(), BlockDataStorageError> {
        self.storage_base.flush_database().await?;
        Ok(())
    }

    pub async fn update_misc_storage_for_genesis_block(
        &self,
        block_id: &BlockId,
        block: &Block,
    ) -> Result<(), BlockDataStorageError> {
        let block_height = 0;
        let time_stamp: TimeStamp = block.get_timestamp() as TimeStamp;
        self.put_best_height(&block_height).await?;
        self.put_time_best_received(&time_stamp).await?;
        self.put_latest_block_id_in_best_chain(block_id).await?;

        Ok(())
    }
}

// latest_block_id_in_best_chain
impl MiscItemStorage {
    pub async fn get_latest_block_id_in_best_chain(
        &self,
    ) -> Result<Option<BlockId>, BlockDataStorageError> {
        let value = self
            .storage_base
            .get(MiscItemDbKey::LatestBlockIdInBestChain)
            .await?;

        if let Some(value) = value {
            let block_id = bincode::deserialize(&value).unwrap();
            Ok(Some(block_id))
        } else {
            Ok(None)
        }
    }

    pub async fn put_latest_block_id_in_best_chain(
        &self,
        block_id: &BlockId,
    ) -> Result<(), BlockDataStorageError> {
        let value = bincode::serialize(block_id).unwrap();
        self.storage_base
            .put(MiscItemDbKey::LatestBlockIdInBestChain, value)
            .await?;
        Ok(())
    }
}

// best_height
impl MiscItemStorage {
    pub async fn get_best_height(&self) -> Result<Option<BlockHeight>, BlockDataStorageError> {
        let value = self.storage_base.get(MiscItemDbKey::BestHeight).await?;

        if let Some(value) = value {
            let block_height = bincode::deserialize(&value).unwrap();
            Ok(Some(block_height))
        } else {
            Ok(None)
        }
    }

    pub async fn put_best_height(
        &self,
        block_height: &BlockHeight,
    ) -> Result<(), BlockDataStorageError> {
        let value = bincode::serialize(block_height).unwrap();
        self.storage_base
            .put(MiscItemDbKey::BestHeight, value)
            .await?;
        Ok(())
    }
}

// time_best_received
impl MiscItemStorage {
    pub async fn get_time_best_received(&self) -> Result<Option<TimeStamp>, BlockDataStorageError> {
        let value = self
            .storage_base
            .get(MiscItemDbKey::TimeBestReceived)
            .await?;

        if let Some(value) = value {
            let time_best_received = bincode::deserialize(&value).unwrap();
            Ok(Some(time_best_received))
        } else {
            Ok(None)
        }
    }

    pub async fn put_time_best_received(
        &self,
        time_best_received: &TimeStamp,
    ) -> Result<(), BlockDataStorageError> {
        let value = bincode::serialize(time_best_received).unwrap();
        self.storage_base
            .put(MiscItemDbKey::TimeBestReceived, value)
            .await?;
        Ok(())
    }
}
